package com.partsoft.dits.integrate;


import java.io.File;
import java.io.IOException;
import java.lang.reflect.Array;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.io.FileUtils;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import com.partsoft.dits.AbstractConfigurableDataDescriptorManager;
import com.partsoft.dits.ConfigurableBean;
import com.partsoft.dits.DescriptorAccepter;
import com.partsoft.dits.DescriptorDepends;
import com.partsoft.dits.EdgeDescriptor;
import com.partsoft.dits.TableDataEdgeDescriptor;
import com.partsoft.dits.dsm.DataSourceDescriptor;

@SuppressWarnings("unchecked")
public abstract class ConfigurableTableDataDescriptorManager<E extends TableDataEdgeDescriptor>
		extends AbstractConfigurableDataDescriptorManager<E> implements
		ConfigurableBean, TableDataDescriptorRegistry<E>, DescriptorDepends<E> {
	
	private String descriptorIdPrefix;
	
	private Set<String> dsNames = Collections.synchronizedSet(new HashSet<String>(2));
	
	private Map<String, Set<E>> tableDescriptors = Collections.synchronizedMap(new HashMap<String, Set<E>>(5));
	
	private Set<Class<? extends EdgeDescriptor>> dependsClasses; 
	{
		dependsClasses = new HashSet<Class<? extends EdgeDescriptor>>(1);
		dependsClasses.add(DataSourceDescriptor.class);
	}
			
	public ConfigurableTableDataDescriptorManager() {}
	
	public ConfigurableTableDataDescriptorManager(String contextConfigLocation, String transformScriptDirectoryLocation, String descriptorIdPrefix) {
		super(contextConfigLocation, transformScriptDirectoryLocation);
		setDescriptorIdPrefix(descriptorIdPrefix);
	}
	
	public ConfigurableTableDataDescriptorManager(String contextConfigLocation, String transformScriptDirectoryLocation, String descriptorIdPrefix, boolean createNoExists) {
		super(contextConfigLocation, transformScriptDirectoryLocation, createNoExists);
		setDescriptorIdPrefix(descriptorIdPrefix);
	}

	/**
	 * 描述符ID前缀
	 * @param descriptorIdPrefix
	 */
	public void setDescriptorIdPrefix(String descriptorIdPrefix) {
		this.descriptorIdPrefix = descriptorIdPrefix;
	}
	
	protected String getDescriptorIdPrefix() {
		return descriptorIdPrefix;
	}
	
	public E getDescriptor(String dsName, String schemaName, String tableName, String messageCode) {
		assertAlreadyInit();
		String publishBeanId = resolveDescriptorIdentifier(dsName, schemaName, tableName, messageCode);
		return hasDescriptor(publishBeanId) ? getDescriptor(publishBeanId) : null;
	}
	
	protected String resolveDescriptorIdentifier(String dsName, String schemaName, String tableName, String messageCode) {
		Assert.hasText(dsName);
		Assert.hasText(tableName);
		Assert.hasText(messageCode);
		StringBuffer idBuffer = new StringBuffer();
		if (StringUtils.hasText(getDescriptorIdPrefix()))
			idBuffer.append(getDescriptorIdPrefix()).append(".");
		idBuffer.append(dsName).append(".");
		if (StringUtils.hasText(schemaName))
			idBuffer.append(schemaName).append(".");
		idBuffer.append(tableName).append(".").append(messageCode);
		return idBuffer.toString(); 
	}
	
	protected String resolveDescriptorIdentifier(E desc) {
		Assert.notNull(desc);
		return StringUtils.hasText(desc.getId()) ? desc.getId() :
			resolveDescriptorIdentifier(desc.getDataSourceName(), 
					desc.getSchemaName(),
					desc.getTableName(),
					desc.getMessageCode());
	}


	@Override
	protected void validateDescriptor(E descriptor) {
		Assert.notNull(descriptor);
		Assert.hasText(descriptor.getDataSourceName(), "必须设置数据源名称");
		Assert.hasText(descriptor.getSchemaName(), "必须设置模式名称");
		Assert.hasText(descriptor.getTableName(), "必须设置数据表名称");
		Assert.hasText(descriptor.getMessageCode(), "必须设置集成消息名称");
	}

	public E[] getDescriptors(String dsName, String schemaName, String tableName) {
		String tfid = resolveTableFullId(dsName, schemaName, tableName);
		E[] returnArray = (E[]) Array.newInstance(getSupportClass(), 0); 
		if (tableDescriptors.containsKey(tfid)) {
			List<E> returnList = new LinkedList<E>(); 
			for (E desc : tableDescriptors.get(tfid)) {
				returnList.add((E) desc.clone());
			}
			returnArray = returnList.toArray(returnArray);
		}
		return returnArray;
	}
	
	public E[] getDescriptors(String dsName, String schemaName, String tableName, DescriptorAccepter<E> accepter) {
		if (accepter == null) return getDescriptors(dsName, schemaName, tableName);
		String tfid = resolveTableFullId(dsName, schemaName, tableName);
		E[] returnArray = (E[]) Array.newInstance(getSupportClass(), 0); 
		if (tableDescriptors.containsKey(tfid)) {
			List<E> returnList = new LinkedList<E>(); 
			for (E desc : tableDescriptors.get(tfid)) {
				if (accepter.accept(desc)) returnList.add((E) desc.clone());
			}
			returnArray = returnList.toArray(returnArray);
		}
		return returnArray;
	}

	public int countDescriptor(String dsName, String schemaName, String tableName) {
		int result = 0;
		for (E desc : getDescriptorMap().values()) {
			if (ObjectUtils.nullSafeEquals(dsName, desc.getDataSourceName()) &&
					ObjectUtils.nullSafeEquals(schemaName, desc.getSchemaName()) &&
					ObjectUtils.nullSafeEquals(tableName, desc.getTableName())){
				result++;
			}
		}
		return result;
	}
	
	public int countDescriptor(String dsName, String schemaName,
			String tableName, DescriptorAccepter<E> accepter) {
		if (accepter == null) return countDescriptor(dsName, schemaName, tableName);
		int result = 0;
		for (E desc : getDescriptorMap().values()) {
			if (ObjectUtils.nullSafeEquals(dsName, desc.getDataSourceName()) &&
					ObjectUtils.nullSafeEquals(schemaName, desc.getSchemaName()) &&
					ObjectUtils.nullSafeEquals(tableName, desc.getTableName()) &&
					accepter.accept(desc)){
				result++;
			}
		}
		return result;

	}
	
	/**
	 * 获得脚本内容(从文件中获取);
	 * @param e 
	 * @return
	 * @throws IOException 如果不成功则抛出
	 */
	protected String loadTransformScriptContent(E e) throws IOException  {
		return FileUtils.readFileToString(new File(getTransformScriptDirectory(), resolveDescriptorScriptRelativeFileName(e)), "utf-8");
	}
	
	/**
	 * 保存脚本内容至文件
	 * @param e
	 * @throws IOException
	 */
	protected void saveTransformScriptContext(E e) throws IOException {
		FileUtils.writeStringToFile(new File(getTransformScriptDirectory(), resolveDescriptorScriptRelativeFileName(e)), e.getTransformScript(), "utf-8");
	}
	
	@Override
	public Set<Class<? extends EdgeDescriptor>> getDependClasses() {
		return dependsClasses;
	}
	
	@Override
	public boolean dependDescriptor(EdgeDescriptor descriptor) {
		return getDependClasses().contains(ClassUtils.getUserClass(descriptor)) ? dsNames.contains(((DataSourceDescriptor)descriptor).getName()) : false;
	}
	
	@Override
	public E[] getDescriptorByDepend(EdgeDescriptor descriptor) {
		E[] returnArray = (E[]) Array.newInstance(getSupportClass(), 0);
		if (dependDescriptor(descriptor)) {
			List<E> returnList = new LinkedList<E>();
			for (E e : getDescriptorMap().values()) {
				if (ObjectUtils.nullSafeEquals(e.getDataSourceName(), ((DataSourceDescriptor)descriptor).getName()))
					returnList.add(e);
			}
			returnArray = returnList.toArray(returnArray);
		}
		return returnArray;
	}

	private String resolveTableFullId(String dsName, String schemaName, String tableName) {
		StringBuffer buffer = new StringBuffer();
		buffer.append(dsName).append(".");
		if (StringUtils.hasText(schemaName)) buffer.append(schemaName).append(".");
		buffer.append(tableName);
		return buffer.toString();
	}
	
	private void appendProperties(E descriptor) {
		dsNames.add(descriptor.getDataSourceName());
		String tfid = resolveTableFullId(descriptor.getDataSourceName(), descriptor.getSchemaName(), descriptor.getTableName());
		Set<E> list = null;
		if (tableDescriptors.containsKey(tfid)) list  = tableDescriptors.get(tfid); else {
			list = new HashSet<E>(5);
			tableDescriptors.put(tfid, list);
		}
		list.add(descriptor);
	}
	
	private void releaseProperties(E descriptor) {
		dsNames.remove(descriptor.getDataSourceName());
		String tfid = resolveTableFullId(descriptor.getDataSourceName(), descriptor.getSchemaName(), descriptor.getTableName());
		Set<E> list = tableDescriptors.get(tfid);
		if (list != null) {
			list.remove(descriptor);
			if (list.size() == 0) tableDescriptors.remove(tfid);
		}
	}
	
	protected void postAddDescriptor(E descriptor) {
		appendProperties(descriptor);
	};
	
	protected void postLoadConfiguredDescriptor(E descriptor) {
		appendProperties(descriptor);
	};
	
	protected void postRemoveDescriptor(E descriptor) {
		releaseProperties(descriptor);
	};
	
	protected void postUnLoadConfiguredDescriptor(E descriptor) {
		releaseProperties(descriptor);
	};
	
}
